创建 CSS 动画的小技巧
Web 动画是提高用户体验(UX)的强大工具,你可以利用 Web 动画来解释复杂的概念,使其更易于理解。CSS 动画(过渡动画和关键帧动画)已成为制作 Web 动画的主流工具之一,现代 CSS 特性几乎为你提供了创建令人难忘,引人入胜的动画所需的所有工具。精心制作的 CSS 动画可以为你的 Web 应用或网站注入生机,吸引用户。
然而,要制作出一个精致的,流畅的,给人感觉良好的动画,还是需要一些制作技巧。在这节课中,我将向大家介绍一些向用户界面添加动画的实战技巧。通过最佳实践和 CSS 动画技术,帮助大家能创建出一个更具包容性的 Web 动画,为你的 Web 应用或网站注入活力,吸引用户,并创造难以忘怀的体验。
那我们开始吧!
使用适合的持续时间
试想一下,当你花了很大的精力创造了一个动画,你可能会坐在那里对着屏幕“欣赏”自己的作用品(奇特的动画),并且很知足、很高兴地看着它们在屏幕上运动。但是,你的用户可能并不像你一样着迷,因此,他们并不像你一样有足够的耐心来“欣赏”你的作品。他们来这里是为了完成某些任务,比如购物、查看信息等,用户并不愿意等待或者花更多的时间,即使你的动画是多么的炫酷和吸引人。
我的建议是:尽量使你的过渡效果尽可能快,而不至于太短,以至于用户可能会错过它们。这是因为,任何一个不良的动画的绝对致命点之一就是它的持续时间太长。
许多研究发现,用户界面上的动画最佳时间在 200ms ~ 500ms 之间。Material Design 在这方面也做过大量的研究和实践,该指南建议我们在移动设备上,动画的持续时间限制在 200ms ~ 300ms 之间为佳。至于平板电脑,持续时间应该比移动设备长 30% 左右,大约在 400ms ~ 450ms 之间。
这些数据都是一种经验法则,该法则可能适用于 Web 上的大多数动画以及动画应用的情境:
- 对于淡入淡出和颜色动画,使用几乎瞬时的持续时间(小于
100ms) - 对于简单效果和相对较小的动画,使用较短的持续时间,大约在
100ms ~ 300ms。还可以用于用户发起的需要反馈的操作,例如触摸、点击和滑动等 - 对于更复杂的效果和较大规模的动画(如页面过渡或屏幕内外的对象移动),使用较长的持续时间(大于
300ms) - 保留慢动画(大于
500ms)用于大幅移动和影响氛围的背景元素
正如上所述,对于简单的反馈动画,比如显示复选框或切换开关,总持续时间应该大致为 100ms 。这个持续时间对用户来说感觉是立即的,创建了物理操纵对象的错觉。100ms 位于可察觉运动的较低端,几乎感觉像是从一个位置瞬间跳到另一个位置,但足以使反馈引人注目。
当动画涉及屏幕发生较大变化时,动画过渡就越显眼。比如,上面的切换按钮动画与下面这个模态框弹出动画就有很大的区别:
我们不要让大的变化发生得太快。比如模态框移入视图,200ms ~ 300ms 的持续时间可能是合适的。元素需要移动的距离越远,保证平稳且不刺眼就越重要(特别是对于对运动敏感的人,比如癫痫障碍的用户)。
如果动画效果不是单一的过渡,而是有连续的过渡,那么你可以在单一过渡动画的持续时间上增加一倍。大多数单一过渡的动画持续时间在 150ms ~ 400ms 之间,效果最佳。因此连续过渡动画总持续时间应该是 300ms ~ 800ms 之间,甚至还可以再加一点点时间。因为你并不希望两个独立的动画匆匆忙忙地就结束了,这会让用户感到突兀。比如一个元素移出,然后另一个元素在此之后移入的效果,就是一个连续过渡效果:
请注意,对象出现或进入屏幕的动画通常需要比对象消失或退出屏幕的动画略长的持续时间。例如弹出窗口可能需要 300ms 来出现,但只需要 200ms 或 250ms 来消失。
当然,上述这些经验法则也有可能不适用于你的动画。不过不用担心,我在这里分享一个我个人经验:
给动画加快,即设置较短的动画持续时间,直到感觉动画太快,然后再稍微减缓一点(加长动画持续时间)
这个过程是一个不断重复的过程。因为,创造一个精致的,使用户感到舒适的动画,本来就是不断调试,不断修复,不断完善的过程。简而言之,任何一个优秀的动画作品都不是一蹴而就的!
最后值得注意的一点是:动画的感觉可能并不总是与它实际持续时间一样长。一个以很慢的缓入开始的动画可能看起来好像没有立即开始;相反,一个带有较长持续时间的动画在技术上可能看起来已经完成了。请记住这一点。
感知就是现实,所以变化的感觉比在代码中技术上持续时间更重要!
使用交错的延迟
在 Web 上有很多使用 CSS 制作的链式动画或交错动画。它们的主要表现是:
- 一个元素上应用了多个动画
- 多个元素上应用了相同的动画
- 多个元素上分别应用了不同的动画
其中链式动画的最大特征之一是,同一个元素上应用了多个不同的动画,例如,我们可以给元素创建一个用于旋转的动画,一个用于调整大小的动画,以及一个用于调整其不透明度的动画。其次就是你在同一个页面上不同的元素上应用了不同的动画,这些元素所应用的动画在播放有一定的时序(先后顺序)。
交错动画的最大特征之一是,在多个元素上应用了同一种类型动画,每个元素上应用的动画在开始播放时有一定的先后顺序。
在构建这两种类型动画时,请不要低估了动画的延迟时间(animation-delay 或 transition-delay)可能产生的效果。
正如你所看到的,上图中左侧动画效果是没有使用延迟时间的效果,右侧动画效果是使用了延迟时间的效果。很明显,左侧的正方形在放大的过程中同时在旋转,而右侧的正方形则不同,它先放大,然后再旋转。
给动画添加延迟时间(交错的延迟)是非常有用的,尤其是在制作链式动画和交错动画效果的时候。例如下面这个链式动画:
<div class="notification">Welcome to the world of Web animation! Please join me on a Web animation tour!</div>@layer animation {
@keyframes bounce {
0% {
transform-origin: center bottom;
}
from,
20%,
53%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
translate: 0 0 0;
}
40%,
43% {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
translate: 0 -30px 0;
scale: 1 1.1;
}
70% {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
translate: 0 15px 0;
scale: 1 1.05;
}
80% {
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
translate: 0 0 0;
scale: 1 0.95;
}
90% {
translate: 0 4px 0;
scale: 1 1.02;
}
}
@keyframes fadeOut {
to {
opacity: 0;
scale: 0;
transform-origin: center top;
}
}
.notification {
animation: bounce 0.5s
linear(
0 0%,
0 2.27%,
0.02 4.53%,
0.04 6.8%,
0.06 9.07%,
0.1 11.33%,
0.14 13.6%,
0.25 18.15%,
0.39 22.7%,
0.56 27.25%,
0.77 31.8%,
1 36.35%,
0.89 40.9%,
0.85 43.18%,
0.81 45.45%,
0.79 47.72%,
0.77 50%,
0.75 52.27%,
0.75 54.55%,
0.75 56.82%,
0.77 59.1%,
0.79 61.38%,
0.81 63.65%,
0.85 65.93%,
0.89 68.2%,
1 72.7%,
0.97 74.98%,
0.95 77.25%,
0.94 79.53%,
0.94 81.8%,
0.94 84.08%,
0.95 86.35%,
0.97 88.63%,
1 90.9%,
0.99 93.18%,
0.98 95.45%,
0.99 97.73%,
1 100%
) both,
fadeOut 0.5s ease-out 2s both;
}
}上面示例在 .notification 元素上同时应用了 bounce 和 fadeOut 两个动画,并且使用 animation-delay 属性,让 fadeOut 动画延迟 3s 之后再开始执行。
再来看另一种链式动画,就是在多个元素上应用不同的动画:
我们使用 CSS 来实现上图中的通知信息的微动效。
@layer animation {
@keyframes abanico {
0% {
rotate: 0deg;
}
7.5% {
rotate: 25deg;
}
30% {
rotate: -25deg;
}
45% {
rotate: 15deg;
}
58% {
rotate: -10deg;
}
70% {
rotate: 5deg;
}
87.5% {
rotate: -2deg;
}
100% {
rotate: 0deg;
}
}
@keyframes badalo {
15% {
translate: -25px;
rotate: 7deg;
}
32.5% {
translate: 10px;
rotate: -7deg;
}
50% {
translate: -15px;
rotate: 7deg;
}
65% {
translate: 5px;
rotate: -7deg;
}
80% {
translate: -15px;
rotate: 7deg;
}
90% {
translate: 0px;
rotate: -7deg;
}
}
@keyframes popup {
0% {
scale: 0.5;
}
100% {
scale: 1;
}
}
.bell__top {
animation: abanico 1000ms ease-in-out infinite;
transform-origin: top;
}
.bell__bottom {
animation: badalo 1000ms ease-in-out infinite;
}
.bell__dot::before {
animation: popup 500ms ease-in-out 150ms infinite alternate;
}
}链式动画相对而言要更为复杂,你可能需要大量的微调来同步所有正在运动的属性,但它们也为我们提供了对每个动画属性选择时间(持续时间、延迟时间)、缓动函数、播放状态的精细控制,使用更少的 CSS 代码创造出更复杂的动画。
就我个人经验而言,如果你正在开发一个链式的动画,那么请不要遗忘动画的编排,你可以整个情境中所有动画想像成是一台舞台剧,你是这台舞台剧的主导,所有的动作(动画)和演员(元素)都由你来安排(时间轴)。
有关于动画编排相关的介绍,请参阅第十二节课《深入了解 CSS 动画的持续时间和延迟时间》!另外,链式动画还会应用到 CSS 多动画的概念。需要注意的是,当同一个元素应用多个不同动画时,动画可能会作用于同一属性,例如在不同的 @keyframes 中作用于 transform 。虽然说单个 CSS 变换可以帮助大家解决一些问题,但单个变换有一个不可规避的原则,即先位移,再旋转,然后再缩放。如果你需要的变换计算并不是这个顺序时,你可以使用动画合成来规避这些不必要的麻烦。有关于这方面更详细的介绍,请参阅《多个 CSS 动画与动画合成:创造更复杂的动画效果》!
接下来,再来看交错动画。我们在上面示例的基础上加点料,当你点击正在运动的小铃铛后,相关的通知信息会以动画的形式从屏幕的右侧飞入:
@keyframes slideIn {
from {
translate: calc(100% + 1rem);
opacity: 0;
}
to {
translate: 0;
opacity: 1;
}
}
@keyframes slideOut {
from {
translate: 0;
opacity: 1;
}
to {
translate: calc(100% + 1rem);
opacity: 0;
}
}
.notification__item {
animation: slideOut .3s ease-out 0s both;
.play & {
animation: slideIn 0.3s ease-in 0s both;
}
}并不难发现,通知信息列表在进场和退场都是同时完成的。效果并不令你满意。如果你在每个列表项添加延迟时间,使每个进入和退出都有不一样的时间,那效果将会好很多:
.notification__item {
--delay-ratio: 0.2s;
/* 这个延迟将在隐藏元素时生效 */
--delay: calc(var(--delay-ratio) * (var(--items) - var(--index)));
animation: slideOut .3s ease-out var(--delay) both;
.play & {
--delay: calc((var(--index) - 1) * var(--delay-ratio));
animation: slideIn 0.3s ease-in var(--delay) both;
}
}上面示例还使用 CSS 自定义属性,通过设置一个与元素索引相等的自定义属性,我们可以使用 calc() 来计算动画延迟时间,并使我们的元素按顺序进行动画。示例中,我以联样式的方式在 HTML 元素上设置了自定义属性。这样,它将与动态内容一起工作:
<div class="notifications" style="--items: 5;">
<div class="notification__item notification--success" style="--index: 1;"></div>
<div class="notification__item notification--info" style="--index: 2;"></div>
<div class="notification__item notification--warning" style="--index: 3;"></div>
<div class="notification__item notification--danger" style="--index: 4;"></div>
<div class="notification__item notification--done" style="--index: 5;"></div>
</div>这种技巧还经常与 Splitting.js 库配合使用来制作文本跳动相关的动画效果。Splitting.js 库将会把文本字符串按字母分组,并且为每个字母分配一个与上述相同的自定义属性,以及用于总项数的另一个自定义属性。
Demo 地址:https://codepen.io/BlogFire/full/mdpvmVy (更多关于 Splitting.js 制作的作品可以点击这里获取)
然而,请记住,适度为佳。类似于这样的动画很容易过度使用,特别是在有许多元素进行动画时。
话虽如此:在更为微妙的情况下应用这种效果是有机会的。如果应用得当,交错的延迟可以帮助提升 Web 动画的效果。
加速和减速
我在本系列多个章节的课程中不断的强调速度和时间对于一个动画是多么的重要,这一原则和迪士尼动画核心原则中的“挤压和位伸”(Squash and Stretch)是非常契合的。
该原则给我们灌输了“感知”的概念或原则,也就是说,灌输一种可能与我们的技术指标不完全一致的页面感知。通过挤压或拉伸运动中的元素,给它们一种更快(或更慢)速度的错觉,再次给我们的页面带来真正的流动感。
在现实世界中,我们几乎永远不会看到任何类型的运动会立即跳到最大速度,或者瞬间完全停止。事实上,Web 中的动画亦是如此。
我们需要知道的是,当动画看起来有些不对劲时,很可能是因为它要么以不自然的突然性开始,要么以不自然的突然性结束。换句话说,要是我们能避免这种运动曲线,那么 Web 上的动画就会看起来可能更“真实”和直观。
即使先抛开 CSS 动画不说,就先说现实生活中的特体运动,任何一个运动都与距离、时间和速度有着紧密的联系。例如,同一段距离,同一样的运动时间,但因速度不同,最终呈现的运动效果也将会不同。在 CSS 动画也不能逃脱这一自然现象。例如下面这个示例,元素都是从最容器最左侧(0 位置)移动到容器最右侧(100%):
@keyframes move {
to {
translate: calc(80vw - 1.25rem);
}
}而且所用的时间(动画持续时间)也是一样,从容器最左侧滑动到容器最右侧,总共用时 3s :
.element {
animation-duration: 3s;
}但因为元素在这个运动过程中速度不一样,最终呈现给你的动画效果有着很大的差异。
通过前面课程的学习,我想你应该知道,在 CSS 动画中,我们通常用缓动曲线来描述动画运动曲线,也就是它的速度。而且,在 CSS 中内置了一些可用的缓动曲线:linear (线性,也是恒速)、ease (默认缓动函数)、ease-in (渐进,也是加速)、ease-out (渐出,也是减速)、ease-in-out (渐进渐出,先加速再减速)、cubic-bezier() (三次贝塞尔曲线)和 steps() (步进缓动):
将这些内置的缓动曲线应用于同一个动画中,即使距离和时间相同,但最终呈现给你的动画效果会有着显著的差异:
.element {
animation-timing-function: var(--animfc, linear);
}正如你所看到的,使用 CSS 定制的缓动曲线是另一种动画技术,它允许我们控制动画或过渡过程中事物减速或加速的速率。虽然这些内置的缓动曲线(时间函数)在某些情况下很方便,也很有用,但它们也非常通常。即使在一些工具或动画库构建的许多动画也容易陷入同样的同质化。这意味着,如果你希望创作出更好的动画效果,应该尽可能的避免使用内置的缓动曲线,应该创建一个定制的缓动曲线。这样做,我们可以有速度变化,比如更快地开始或结束的动画,甚至可以超越目标然后弹回。
即使是轻微的,我仍然建议在你使用cubic-bezier() 函数在动画的缓动曲线中添加一点点的缓动。这种微小但可察觉的加速和(或)减速可能是你的动画给人感觉平滑与稍显不足的关键之处。
例如下面这个示例,四个正方形(运动元素)都围绕着自身中心点旋转了一圈 1turn (即 360deg),但它们分别应用了不同的三次贝塞尔曲线:
<div class="element" style="--easing: cubic-bezier(.9,.03,.69,.22);">A</div>
<div class="element" style="--easing: cubic-bezier(.17,.84,.44,1);">B</div>
<div class="element" style="--easing: cubic-bezier(.62,.16,.13,1.01);">C</div>
<div class="element" style="--easing: cubic-bezier(.5,-0.53,.14,1.23);">D</div>@keyframes rotate {
50%, 100% {
rotate: 1turn;
}
}
.element {
animation: rotate 2s var(--easing, ease) infinite;
}你可能已经发现了,上面示例中的 A 和 D 两个正方形的旋转动效似乎开始或结束得太突然,而 C 正方形的旋转效果要好得多,因为它的自定义曲线在更为优雅的运动中缓入缓出,实现了加速和减速。其实 D 正方形的旋转效果也还不错,它的运动曲线中加入了类似弹簧运动曲线,使得整个旋转效果带有一定的惯性。
这个示例再次向大家阐述了“即使动画的持续时间相同,运动距离相同,但不同的缓动曲线也能真正改变动画感觉”。如果你想发挥动画的最大潜力,那么应该超越最常见的缓动曲线(内置的缓动曲线)。
:root {
/* Default Easing Function */
--linear: cubic-bezier(0, 0, 1, 1);
--ease: cubic-bezier(0.25, 0.1, 0.25, 1);
--ease-in: cubic-bezier(0.42, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.58, 1);
--ease-in-out: cubic-bezier(0.42, 0, 0.58, 1);
/* Sine Easing Function */
--sine-in: cubic-bezier(0.47, 0, 0.745, 0.715);
--sine-out: cubic-bezier(0.39, 0.575, 0.565, 1);
--sine-in-out: cubic-bezier(0.445, 0.05, 0.55, 0.95);
--ease-in-sine: cubic-bezier(0.12, 0, 0.39, 0);
--ease-out-sine: cubic-bezier(0.61, 1, 0.88, 1);
--ease-in-out-sine: cubic-bezier(0.37, 0, 0.63, 1);
--ease-out-in-sine: cubic-bezier(0.05, 0.445, 0.95, 0.55);
/* Circular Easing Function */
--circ-in: cubic-bezier(0.6, 0.04, 0.98, 0.335);
--circ-out: cubic-bezier(0.075, 0.82, 0.165, 1);
--circ-in-out: cubic-bezier(0.785, 0.135, 0.15, 0.86);
--ease-in-circ: cubic-bezier(0.55, 0, 1, 0.45);
--ease-out-circ: cubic-bezier(0, 0.55, 0.45, 1);
--ease-in-out-circ: cubic-bezier(0.85, 0, 0.15, 1);
--ease-out-in-circ: cubic-bezier(0.135, 0.785, 0.86, 0.15);
/* Quadratic Easing Function */
--quad-in: cubic-bezier(0.55, 0.085, 0.68, 0.53);
--quad-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--quad-in-out: cubic-bezier(0.455, 0.03, 0.515, 0.955);
--ease-in-quad: cubic-bezier(0.11, 0, 0.5, 0);
--ease-out-quad: cubic-bezier(0.5, 1, 0.89, 1);
--ease-in-out-quad: cubic-bezier(0.45, 0, 0.55, 1);
--ease-out-in-quad: cubic-bezier(0.03, 0.455, 0.955, 0.515);
/* Cubic Easing Function */
--cubic-in: cubic-bezier(0.55, 0.055, 0.675, 0.19);
--cubic-out: cubic-bezier(0.215, 0.61, 0.355, 1);
--cubic-in-out: cubic-bezier(0.645, 0.045, 0.355, 1);
--ease-in-cubic: cubic-bezier(0.32, 0, 0.67, 0);
--ease-out-cubic: cubic-bezier(0.33, 1, 0.68, 1);
--ease-in-out-cubic: cubic-bezier(0.65, 0, 0.35, 1);
--ease-out-in-cubic: cubic-bezier(0.045, 0.645, 1.0, 0.355);
/* Quartic Easing Function */
--quart-in: cubic-bezier(0.895, 0.03, 0.685, 0.22);
--quart-out: cubic-bezier(0.165, 0.84, 0.44, 1);
--quart-in-out: cubic-bezier(0.77, 0, 0.175, 1);
--ease-in-quart: cubic-bezier(0.5, 0, 0.75, 0);
--ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
--ease--in-out-quart: cubic-bezier(0.76, 0, 0.24, 1);
--ease-out-in-quart: cubic-bezier(0.0, 0.77, 1.0, 0.175);
/* Quintic Easing Function */
--quint-in: cubic-bezier(0.755, 0.05, 0.855, 0.06);
--quint-out: cubic-bezier(0.23, 1, 0.32, 1);
--quint-in-out: cubic-bezier(0.86, 0, 0.07, 1);
--ease-in-quint: cubic-bezier(0.64, 0, 0.78, 0);
--ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
--ease-in-out-quint: cubic-bezier(0.83, 0, 0.17, 1);
--ease-out-in-quint: cubic-bezier(0.0, 0.86, 1.0, 0.07);
/* Exponential Easing Function */
--expo-in: cubic-bezier(0.95, 0.05, 0.795, 0.035);
--expo-out: cubic-bezier(0.19, 1, 0.22, 1);
--expo-in-out: cubic-bezier(1, 0, 0, 1);
--ease-in-expo: cubic-bezier(0.7, 0, 0.84, 0);
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out-expo: cubic-bezier(0.87, 0, 0.13, 1);
--ease-out-in-expo: cubic-bezier(0.0, 1.0, 1.0, 0.0);
/* Back Easing Function */
--back-in: cubic-bezier(0.6, -0.28, 0.735, 0.045);
--back-out: cubic-bezier(0.175, 0.885, 0.32, 1.275);
--back-in-out: cubic-bezier(0.68, -0.55, 0.265, 1.55);
--ease-in-back: cubic-bezier(0.36, 0, 0.66, -0.56);
--ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-in-out-back: cubic-bezier(0.68, -0.6, 0.32, 1.6);
--ease-out-in-back: cubic-bezier(0.25, 1.25, 0.75, -0.25);
/* Epic Easing Function*/
--ease-in-epic: cubic-bezier(0.25, 0.0, 0.75, 0.25);
--ease-out-epic: cubic-bezier(0.25, 0.75, 0.75, 1.0);
--ease-in-out-epic: cubic-bezier(0.75, 0.25, 0.25, 0.75);
--ease-out-in-epic:cubic-bezier(0.25, 0.75, 0.75, 0.25);
}你可以借助 cubic-bezier.com 来调试缓动曲线:
或者通过浏览器开发者工具的样式面板(Styles)来调整动画的缓动函数,每个主流浏览器都有一个缓动曲线面板,可以作为一个沙盒来尝试不同的选项并进行调整。
需要知道的是,三次贝塞尔 cubic-bezier() 函数是很强大,但很难通过它微出出类似于弹簧的运动曲线。庆幸的是,你可以通过 linear() 函数来获得更为复杂的运动曲线:
:root {
--linear: linear(0, 1);
--quad-in: linear( 0, 0.0039, 0.0156, 0.0352, 0.0625, 0.0977, 0.1407, 0.1914, 0.2499, 0.3164, 0.3906 62.5%, 0.5625, 0.7656, 1 );
--quad-out: linear( 0, 0.2342, 0.4374, 0.6093 37.49%, 0.6835, 0.7499, 0.8086, 0.8593, 0.9023, 0.9375, 0.9648, 0.9844, 0.9961, 1 );
--quad-in-out: linear( 0, 0.0027, 0.0106 7.29%, 0.0425, 0.0957, 0.1701 29.16%, 0.2477, 0.3401 41.23%, 0.5982 55.18%, 0.7044 61.56%, 0.7987, 0.875 75%, 0.9297, 0.9687, 0.9922, 1 );
--power-1-in: linear( 0, 0.0039, 0.0156, 0.0352, 0.0625, 0.0977, 0.1407, 0.1914, 0.2499, 0.3164, 0.3906 62.5%, 0.5625, 0.7656, 1 );
--power-1-out: linear( 0, 0.2342, 0.4374, 0.6093 37.49%, 0.6835, 0.7499, 0.8086, 0.8593, 0.9023, 0.9375, 0.9648, 0.9844, 0.9961, 1 );
--power-1-in-out: linear( 0, 0.0027, 0.0106 7.29%, 0.0425, 0.0957, 0.1701 29.16%, 0.2477, 0.3401 41.23%, 0.5982 55.18%, 0.7044 61.56%, 0.7987, 0.875 75%, 0.9297, 0.9687, 0.9922, 1 );
--cubic-in: linear( 0, 0.0014 11.11%, 0.0071 19.24%, 0.0188 26.6%, 0.037 33.33%, 0.0634 39.87%, 0.0978 46.07%, 0.1407 52.02%, 0.1925 57.74%, 0.2559 63.49%, 0.3295 69.07%, 0.4135 74.5%, 0.5083 79.81%, 0.6141 85%, 0.7312 90.09%, 1 );
--cubic-out: linear( 0, 0.2688 9.91%, 0.3859 15%, 0.4917 20.19%, 0.5865 25.5%, 0.6705 30.93%, 0.7441 36.51%, 0.8075 42.26%, 0.8593 47.98%, 0.9022 53.93%, 0.9366 60.13%, 0.963 66.67%, 0.9812 73.4%, 0.9929 80.76%, 0.9986 88.89%, 1 );
--cubic-in-out: linear( 0, 0.0036 9.62%, 0.0185 16.66%, 0.0489 23.03%, 0.0962 28.86%, 0.1705 34.93%, 0.269 40.66%, 0.3867 45.89%, 0.5833 52.95%, 0.683 57.05%, 0.7829 62.14%, 0.8621 67.46%, 0.8991 70.68%, 0.9299 74.03%, 0.9545 77.52%, 0.9735 81.21%, 0.9865 85%, 0.9949 89.15%, 1 );
--power-2-in: linear( 0, 0.0014 11.11%, 0.0071 19.24%, 0.0188 26.6%, 0.037 33.33%, 0.0634 39.87%, 0.0978 46.07%, 0.1407 52.02%, 0.1925 57.74%, 0.2559 63.49%, 0.3295 69.07%, 0.4135 74.5%, 0.5083 79.81%, 0.6141 85%, 0.7312 90.09%, 1 );
--power-2-out: linear( 0, 0.2688 9.91%, 0.3859 15%, 0.4917 20.19%, 0.5865 25.5%, 0.6705 30.93%, 0.7441 36.51%, 0.8075 42.26%, 0.8593 47.98%, 0.9022 53.93%, 0.9366 60.13%, 0.963 66.67%, 0.9812 73.4%, 0.9929 80.76%, 0.9986 88.89%, 1 );
--power-2-in-out: linear( 0, 0.0036 9.62%, 0.0185 16.66%, 0.0489 23.03%, 0.0962 28.86%, 0.1705 34.93%, 0.269 40.66%, 0.3867 45.89%, 0.5833 52.95%, 0.683 57.05%, 0.7829 62.14%, 0.8621 67.46%, 0.8991 70.68%, 0.9299 74.03%, 0.9545 77.52%, 0.9735 81.21%, 0.9865 85%, 0.9949 89.15%, 1 );
--quart-in: linear( 0, 0.0039 25%, 0.0117 32.89%, 0.0248 39.68%, 0.0457 46.22%, 0.0743 52.21%, 0.1113 57.77%, 0.1575 63%, 0.218 68.33%, 0.2901 73.39%, 0.3745 78.23%, 0.4718 82.88%, 0.5827 87.37%, 0.7074 91.71%, 0.8462 95.91%, 1 );
--quart-out: linear( 0, 0.1538 4.09%, 0.2926 8.29%, 0.4173 12.63%, 0.5282 17.12%, 0.6255 21.77%, 0.7099 26.61%, 0.782 31.67%, 0.8425 37%, 0.8887 42.23%, 0.9257 47.79%, 0.9543 53.78%, 0.9752 60.32%, 0.9883 67.11%, 0.9961 75%, 1 );
--quart-in-out: linear( 0, 0.0029 13.8%, 0.0184 21.9%, 0.0339 25.51%, 0.0551 28.81%, 0.0827 31.88%, 0.1168 34.76%, 0.1962 39.57%, 0.3005 44.02%, 0.4084 47.53%, 0.6242 53.45%, 0.7493 57.93%, 0.8495 62.97%, 0.8888 65.67%, 0.9213 68.51%, 0.9629 73.9%, 0.9876 80.16%, 0.998 87.5%, 1 );
--power-3-in: linear( 0, 0.0039 25%, 0.0117 32.89%, 0.0248 39.68%, 0.0457 46.22%, 0.0743 52.21%, 0.1113 57.77%, 0.1575 63%, 0.218 68.33%, 0.2901 73.39%, 0.3745 78.23%, 0.4718 82.88%, 0.5827 87.37%, 0.7074 91.71%, 0.8462 95.91%, 1 );
--power-3-out: linear( 0, 0.1538 4.09%, 0.2926 8.29%, 0.4173 12.63%, 0.5282 17.12%, 0.6255 21.77%, 0.7099 26.61%, 0.782 31.67%, 0.8425 37%, 0.8887 42.23%, 0.9257 47.79%, 0.9543 53.78%, 0.9752 60.32%, 0.9883 67.11%, 0.9961 75%, 1 );
--power-3-in-out: linear( 0, 0.0029 13.8%, 0.0184 21.9%, 0.0339 25.51%, 0.0551 28.81%, 0.0827 31.88%, 0.1168 34.76%, 0.1962 39.57%, 0.3005 44.02%, 0.4084 47.53%, 0.6242 53.45%, 0.7493 57.93%, 0.8495 62.97%, 0.8888 65.67%, 0.9213 68.51%, 0.9629 73.9%, 0.9876 80.16%, 0.998 87.5%, 1 );
--quint-in: linear( 0, 0.0024 29.91%, 0.008 38.03%, 0.0179 44.72%, 0.035 51.16%, 0.0595 56.88%, 0.0922 62.08%, 0.1338 66.88%, 0.1914 71.85%, 0.262 76.5%, 0.3461 80.88%, 0.4447 85.04%, 0.5587 89.01%, 0.689 92.82%, 0.8359 96.48%, 1 );
--quint-out: linear( 0, 0.1641 3.52%, 0.311 7.18%, 0.4413 10.99%, 0.5553 14.96%, 0.6539 19.12%, 0.738 23.5%, 0.8086 28.15%, 0.8662 33.12%, 0.9078 37.92%, 0.9405 43.12%, 0.965 48.84%, 0.9821 55.28%, 0.992 61.97%, 0.9976 70.09%, 1 );
--quint-in-out: linear( 0, 0.0012 14.95%, 0.0089 22.36%, 0.0297 28.43%, 0.0668 33.43%, 0.0979 36.08%, 0.1363 38.55%, 0.2373 43.07%, 0.3675 47.01%, 0.5984 52.15%, 0.7121 55.23%, 0.8192 59.21%, 0.898 63.62%, 0.9297 66.23%, 0.9546 69.06%, 0.9733 72.17%, 0.9864 75.67%, 0.9982 83.73%, 1 );
--power-4-in: linear( 0, 0.0024 29.91%, 0.008 38.03%, 0.0179 44.72%, 0.035 51.16%, 0.0595 56.88%, 0.0922 62.08%, 0.1338 66.88%, 0.1914 71.85%, 0.262 76.5%, 0.3461 80.88%, 0.4447 85.04%, 0.5587 89.01%, 0.689 92.82%, 0.8359 96.48%, 1 );
--power-4-out: linear( 0, 0.1641 3.52%, 0.311 7.18%, 0.4413 10.99%, 0.5553 14.96%, 0.6539 19.12%, 0.738 23.5%, 0.8086 28.15%, 0.8662 33.12%, 0.9078 37.92%, 0.9405 43.12%, 0.965 48.84%, 0.9821 55.28%, 0.992 61.97%, 0.9976 70.09%, 1 );
--power-4-in-out: linear( 0, 0.0012 14.95%, 0.0089 22.36%, 0.0297 28.43%, 0.0668 33.43%, 0.0979 36.08%, 0.1363 38.55%, 0.2373 43.07%, 0.3675 47.01%, 0.5984 52.15%, 0.7121 55.23%, 0.8192 59.21%, 0.898 63.62%, 0.9297 66.23%, 0.9546 69.06%, 0.9733 72.17%, 0.9864 75.67%, 0.9982 83.73%, 1 );
--strong-in: linear( 0, 0.0024 29.91%, 0.008 38.03%, 0.0179 44.72%, 0.035 51.16%, 0.0595 56.88%, 0.0922 62.08%, 0.1338 66.88%, 0.1914 71.85%, 0.262 76.5%, 0.3461 80.88%, 0.4447 85.04%, 0.5587 89.01%, 0.689 92.82%, 0.8359 96.48%, 1 );
--strong-out: linear( 0, 0.1641 3.52%, 0.311 7.18%, 0.4413 10.99%, 0.5553 14.96%, 0.6539 19.12%, 0.738 23.5%, 0.8086 28.15%, 0.8662 33.12%, 0.9078 37.92%, 0.9405 43.12%, 0.965 48.84%, 0.9821 55.28%, 0.992 61.97%, 0.9976 70.09%, 1 );
--strong-in-out: linear( 0, 0.0012 14.95%, 0.0089 22.36%, 0.0297 28.43%, 0.0668 33.43%, 0.0979 36.08%, 0.1363 38.55%, 0.2373 43.07%, 0.3675 47.01%, 0.5984 52.15%, 0.7121 55.23%, 0.8192 59.21%, 0.898 63.62%, 0.9297 66.23%, 0.9546 69.06%, 0.9733 72.17%, 0.9864 75.67%, 0.9982 83.73%, 1 );
--elastic-in: linear( 0, 0.0019 13.34%, -0.0056 27.76%, -0.0012 31.86%, 0.0147 39.29%, 0.0161 42.46%, 0.0039 46.74%, -0.0416 54.3%, -0.046 57.29%, -0.0357, -0.0122 61.67%, 0.1176 69.29%, 0.1302 70.79%, 0.1306 72.16%, 0.1088 74.09%, 0.059 75.99%, -0.0317 78.19%, -0.3151 83.8%, -0.3643 85.52%, -0.3726, -0.3705 87.06%, -0.3463, -0.2959 89.3%, -0.1144 91.51%, 0.7822 97.9%, 1 );
--elastic-out: linear( 0, 0.2178 2.1%, 1.1144 8.49%, 1.2959 10.7%, 1.3463 11.81%, 1.3705 12.94%, 1.3726, 1.3643 14.48%, 1.3151 16.2%, 1.0317 21.81%, 0.941 24.01%, 0.8912 25.91%, 0.8694 27.84%, 0.8698 29.21%, 0.8824 30.71%, 1.0122 38.33%, 1.0357, 1.046 42.71%, 1.0416 45.7%, 0.9961 53.26%, 0.9839 57.54%, 0.9853 60.71%, 1.0012 68.14%, 1.0056 72.24%, 0.9981 86.66%, 1 );
--elastic-in-out: linear( 0, 0.0009 8.51%, -0.0047 19.22%, 0.0016 22.39%, 0.023 27.81%, 0.0237 30.08%, 0.0144 31.81%, -0.0051 33.48%, -0.1116 39.25%, -0.1181 40.59%, -0.1058 41.79%, -0.0455, 0.0701 45.34%, 0.9702 55.19%, 1.0696 56.97%, 1.0987 57.88%, 1.1146 58.82%, 1.1181 59.83%, 1.1092 60.95%, 1.0057 66.48%, 0.986 68.14%, 0.9765 69.84%, 0.9769 72.16%, 0.9984 77.61%, 1.0047 80.79%, 0.9991 91.48%, 1 );
--bounce-in: linear( 0, 0.0117, 0.0156, 0.0117, 0, 0.0273, 0.0468, 0.0586, 0.0625, 0.0586, 0.0468, 0.0273, 0 27.27%, 0.1093, 0.1875 36.36%, 0.2148, 0.2343, 0.2461, 0.25, 0.2461, 0.2344, 0.2148 52.28%, 0.1875 54.55%, 0.1095, 0, 0.2341, 0.4375, 0.6092, 0.75, 0.8593, 0.9375 90.91%, 0.9648, 0.9843, 0.9961, 1 );
--bounce-out: linear( 0, 0.0039, 0.0157, 0.0352, 0.0625 9.09%, 0.1407, 0.25, 0.3908, 0.5625, 0.7654, 1, 0.8907, 0.8125 45.45%, 0.7852, 0.7657, 0.7539, 0.75, 0.7539, 0.7657, 0.7852, 0.8125 63.64%, 0.8905, 1 72.73%, 0.9727, 0.9532, 0.9414, 0.9375, 0.9414, 0.9531, 0.9726, 1, 0.9883, 0.9844, 0.9883, 1 );
--bounce-in-out: linear( 0, 0.0078, 0, 0.0235, 0.0313, 0.0235, 0.0001 13.63%, 0.0549 15.92%, 0.0938, 0.1172, 0.125, 0.1172, 0.0939 27.26%, 0.0554 29.51%, 0.0003 31.82%, 0.2192, 0.3751 40.91%, 0.4332, 0.4734 45.8%, 0.4947 48.12%, 0.5027 51.35%, 0.5153 53.19%, 0.5437, 0.5868 57.58%, 0.6579, 0.7504 62.87%, 0.9999 68.19%, 0.9453, 0.9061, 0.8828, 0.875, 0.8828, 0.9063, 0.9451 84.08%, 0.9999 86.37%, 0.9765, 0.9688, 0.9765, 1, 0.9922, 1 );
--expo-in: linear( 0, 0.0085 31.26%, 0.0167 40.94%, 0.0289 48.86%, 0.0471 55.92%, 0.0717 61.99%, 0.1038 67.32%, 0.1443 72.07%, 0.1989 76.7%, 0.2659 80.89%, 0.3465 84.71%, 0.4419 88.22%, 0.554 91.48%, 0.6835 94.51%, 0.8316 97.34%, 1 );
--expo-out: linear( 0, 0.1684 2.66%, 0.3165 5.49%, 0.446 8.52%, 0.5581 11.78%, 0.6535 15.29%, 0.7341 19.11%, 0.8011 23.3%, 0.8557 27.93%, 0.8962 32.68%, 0.9283 38.01%, 0.9529 44.08%, 0.9711 51.14%, 0.9833 59.06%, 0.9915 68.74%, 1 );
--expo-in-out: linear( 0, 0.0053 17.18%, 0.0195 26.59%, 0.0326 30.31%, 0.0506 33.48%, 0.0744 36.25%, 0.1046 38.71%, 0.1798 42.62%, 0.2846 45.93%, 0.3991 48.37%, 0.6358 52.29%, 0.765 55.45%, 0.8622 59.3%, 0.8986 61.51%, 0.9279 63.97%, 0.9481 66.34%, 0.9641 69.01%, 0.9856 75.57%, 0.9957 84.37%, 1 );
--circ-in: linear( -0, 0.0048 9.8%, 0.0192 19.5%, 0.043 29.02%, 0.0761 38.26%, 0.1181 47.13%, 0.1685 55.56%, 0.227 63.44%, 0.2929 70.71%, 0.3656 77.3%, 0.4445 83.15%, 0.5285 88.19%, 0.6173 92.39%, 0.7099 95.7%, 0.805 98.08%, 0.9021 99.52%, 1 );
--circ-out: linear( 0, 0.0979 0.48%, 0.195 1.92%, 0.2901 4.3%, 0.3827 7.61%, 0.4715 11.81%, 0.5555 16.85%, 0.6344 22.7%, 0.7071 29.29%, 0.773 36.56%, 0.8315 44.44%, 0.8819 52.87%, 0.9239 61.74%, 0.957 70.98%, 0.9808 80.5%, 0.9952 90.2%, 1 );
--circ-in-out: linear( -0, 0.0033 5.75%, 0.0132 11.43%, 0.0296 16.95%, 0.0522 22.25%, 0.0808 27.25%, 0.1149 31.89%, 0.1542 36.11%, 0.1981 39.85%, 0.2779 44.79%, 0.3654 48.15%, 0.4422 49.66%, 0.5807 50.66%, 0.6769 53.24%, 0.7253 55.37%, 0.7714 58.01%, 0.8142 61.11%, 0.8536 64.65%, 0.9158 72.23%, 0.9619 80.87%, 0.9904 90.25%, 1 );
--sine-in: linear( 0, 0.0035, 0.0141 10.7%, 0.0318 16.09%, 0.0566 21.51%, 0.0885 26.98%, 0.1278 32.53%, 0.2288 43.93%, 0.3563 55.48%, 0.5171 67.92%, 0.7139 81.53%, 1 );
--sine-out: linear( 0, 0.2861 18.47%, 0.4829 32.08%, 0.6437 44.52%, 0.7712 56.07%, 0.8722 67.47%, 0.9115 73.02%, 0.9434 78.49%, 0.9682 83.91%, 0.9859 89.3%, 0.9965, 1 );
--sine-in-out: linear( 0, 0.007 5.35%, 0.0282 10.75%, 0.0638 16.26%, 0.1144 21.96%, 0.1833 28.16%, 0.2717 34.9%, 0.6868 62.19%, 0.775 68.54%, 0.8457 74.3%, 0.9141 81.07%, 0.9621 87.52%, 0.9905 93.8%, 1 );
--back-in: linear( 0, -0.0029 4.31%, -0.0119 9.02%, -0.0838 31.27%, -0.0956 36.64%, -0.1 41.45%, -0.0953 47.03%, -0.0792 52.25%, -0.0512 57.19%, -0.0111 61.92%, 0.0512 67.19%, 0.131 72.27%, 0.2284 77.18%, 0.3443 81.96%, 0.479 86.62%, 0.6329 91.17%, 0.8065 95.63%, 1 );
--back-out: linear( 0, 0.1935 4.37%, 0.3671 8.83%, 0.521 13.38%, 0.6557 18.04%, 0.7716 22.82%, 0.869 27.73%, 0.9488 32.81%, 1.0111 38.08%, 1.0512 42.81%, 1.0792 47.75%, 1.0953 52.97%, 1.1 58.55%, 1.0956 63.36%, 1.0838 68.73%, 1.0119 90.98%, 1.0029 95.69%, 1 );
--back-in-out: linear( 0, -0.0077 5.2%, -0.0452 16.98%, -0.0493 22.35%, -0.0418 25.57%, -0.0258 28.57%, -0.0007 31.42%, 0.0335 34.15%, 0.1242 39.03%, 0.2505 43.65%, 0.3844 47.35%, 0.656 53.68%, 0.81 58.37%, 0.9282 63.52%, 0.9719 66.23%, 1.0055 69.04%, 1.0255 71.4%, 1.0396 73.87%, 1.0477 76.48%, 1.05 79.27%, 1.0419 84.36%, 1.0059 95.49%, 1 );
--spring-1: linear(0, 0.006, 0.025 2.8%, 0.101 6.1%, 0.539 18.9%, 0.721 25.3%, 0.849 31.5%,0.937 38.1%, 0.968 41.8%, 0.991 45.7%, 1.006 50.1%, 1.015 55%, 1.017 63.9%,1.001);
--spring-2: linear(0, 0.007, 0.029 2.2%, 0.118 4.7%, 0.625 14.4%, 0.826 19%, 0.902, 0.962,1.008 26.1%, 1.041 28.7%, 1.064 32.1%, 1.07 36%, 1.061 40.5%, 1.015 53.4%,0.999 61.6%, 0.995 71.2%, 1);
--spring-3: linear(0, 0.009, 0.035 2.1%, 0.141 4.4%, 0.723 12.9%, 0.938 16.7%, 1.017, 1.077,1.121, 1.149 24.3%, 1.159, 1.163, 1.161, 1.154 29.9%, 1.129 32.8%,1.051 39.6%, 1.017 43.1%, 0.991, 0.977 51%, 0.974 53.8%, 0.975 57.1%,0.997 69.8%, 1.003 76.9%, 1);
--spring-4: linear(0, 0.009, 0.037 1.7%, 0.153 3.6%, 0.776 10.3%, 1.001, 1.142 16%, 1.185, 1.209 19%, 1.215 19.9% 20.8%, 1.199, 1.165 25%, 1.056 30.3%, 1.008 33%, 0.973,0.955 39.2%, 0.953 41.1%, 0.957 43.3%, 0.998 53.3%, 1.009 59.1% 63.7%,0.998 78.9%, 1);
--spring-5: linear(0, 0.01, 0.04 1.6%, 0.161 3.3%, 0.816 9.4%, 1.046, 1.189 14.4%, 1.231,1.254 17%, 1.259, 1.257 18.6%, 1.236, 1.194 22.3%, 1.057 27%, 0.999 29.4%,0.955 32.1%, 0.942, 0.935 34.9%, 0.933, 0.939 38.4%, 1 47.3%, 1.011,1.017 52.6%, 1.016 56.4%, 1 65.2%, 0.996 70.2%, 1.001 87.2%, 1);
--bounce-1: linear(0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141, 0.191, 0.25, 0.316, 0.391 36.8%,0.563, 0.766, 1 58.8%, 0.946, 0.908 69.1%, 0.895, 0.885, 0.879, 0.878, 0.879,0.885, 0.895, 0.908 89.7%, 0.946, 1);
--bounce-2: linear(0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 15.1%, 0.25, 0.391, 0.562, 0.765,1, 0.892 45.2%, 0.849, 0.815, 0.788, 0.769, 0.757, 0.753, 0.757, 0.769, 0.788,0.815, 0.85, 0.892 75.2%, 1 80.2%, 0.973, 0.954, 0.943, 0.939, 0.943, 0.954,0.973, 1);
--bounce-3: linear(0, 0.004, 0.016, 0.035, 0.062, 0.098, 0.141 11.4%, 0.25, 0.39, 0.562, 0.764,1 30.3%, 0.847 34.8%, 0.787, 0.737, 0.699, 0.672, 0.655, 0.65, 0.656, 0.672,0.699, 0.738, 0.787, 0.847 61.7%, 1 66.2%, 0.946, 0.908, 0.885 74.2%, 0.879,0.878, 0.879, 0.885 79.5%, 0.908, 0.946, 1 87.4%, 0.981, 0.968, 0.96, 0.957, 0.96, 0.968, 0.981, 1);
--bounce-4: linear(0, 0.004, 0.016 3%, 0.062, 0.141, 0.25, 0.391, 0.562 18.2%, 1 24.3%, 0.81,0.676 32.3%, 0.629, 0.595, 0.575, 0.568, 0.575, 0.595, 0.629, 0.676 48.2%,0.811, 1 56.2%, 0.918, 0.86, 0.825, 0.814, 0.825, 0.86, 0.918, 1 77.2%,0.94 80.6%, 0.925, 0.92, 0.925, 0.94 87.5%, 1 90.9%, 0.974, 0.965, 0.974, 1);
--bounce-5: linear(0, 0.004, 0.016 2.5%, 0.063, 0.141, 0.25 10.1%, 0.562, 1 20.2%, 0.783, 0.627,0.534 30.9%, 0.511, 0.503, 0.511, 0.534 38%, 0.627, 0.782, 1 48.7%, 0.892,0.815, 0.769 56.3%, 0.757, 0.753, 0.757, 0.769 61.3%, 0.815, 0.892, 1 68.8%,0.908 72.4%, 0.885, 0.878, 0.885, 0.908 79.4%, 1 83%, 0.954 85.5%, 0.943,0.939, 0.943, 0.954 90.5%, 1 93%, 0.977, 0.97, 0.977, 1);
}然而,无论你如何定义你的缓动曲线,我建议你花些时间进行微调。尽可能的在动画中使用 cubic-bezier() 或 linear() 函数定义的缓动曲线。
不过,有一点我需要说明,虽然 cubic-bezier() 或 linear() 函数定义的缓动曲线能使动画更接近自然物体的运动规律(不是立即加速或立即减速),但并不能说突然开始和瞬间停止就一无事处,或者说不能用。在此,我只是建议少用,或尽可能避免不使用。但在一些情境中,它们也是可以使用的。比如,用户看不到的情境中就可以用,最常见就是淡入的动画效果,该效果突然开始也可能没太大问题,因为动画的开始在第一眼可能不可感知。同样,反之亦然;如果一个元素正在淡出到 opacity: 0,动画瞬间结束也不太重要,因为它在最后也不可见。
除此之外,动画的缓动曲线和时间(持续时间和延迟时间)类似。要是同一个元素应用多个动画,或者同时对多个属性进行过渡处理时,应该考虑应用不同的缓动曲线。例如,当你通过 scale 缩放元素的同时还使用 opacity 改变元素的透明度。你可以对 scale 和 opacity 两属性应用相同的缓动曲线:
.element {
transition: all cubic-bezier(0.5, 0, 0.5, 1) 0.5s;
}
@keyframes animn {
from {
opacity: 0;
scale:0;
}
}
.element {
animation: animn 0.5s cubic-bezier(0.5, 0, 0.5, 1) forwards;
}取决于实际情况,这样应用缓动曲线,动画效果可能看起来也还不错。然而,在某些情况下,对于正在过渡的每个属性,相同的缓动曲线可能并不适用。例如,适用于 scale 属性的缓动曲线可能不太适用于 opacity 属性。此时,为每个过渡属性设置独特的缓动曲线会更妥当些。而且,在 CSS 为不同的过渡属性设置独特的缓动曲线并不复杂。
我们可以通过拆分 @keyframes 或指定多个过渡属性,然后为每个属性指定不同的缓动曲线,因为 CSS 的过渡动画(transiton)和关键帧动画(animation)都接受以逗号分隔(,)的多个值。要是使用多个值来改造上面示例的话,它看起来像下面这样:
.element {
transition: opacity linear 0.5s, scale cubic-bezier(0.5, 0, 0.5, 1) 0.5s;
}
@keyframes scale {
from {
scale:0;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
}
.element {
animation: scale 0.5s cubic-bezier(0.5, 0, 0.5, 1) forwards, fadeIn 0.5s linear forwards;
}哪个更好?这完全取决于你想要实现的效果。
同样,这种情况可能并不经常发生,但当发生时,这个技巧非常方便,而且也容易被忽视,大多数 Web 开发者会忽略这种小技巧的使用。事实上,你还可以改变每个属性的持续时间,但要小心,如果应用的不好,会使你的动画效果变得不协调。
针对于该情境,我依然建议你使用动画编排或者借助浏览器开发者工具的“动画”调试面板来对它们进行微调。
还有一点需要知道的是,在 CSS 中,使用 cubic-bezier() 和 linear() 函数 是可以获得各种缓动曲线,但不难发现,这些缓动曲线通常分为三种类型:开始慢 (如 ease-in)、结束慢 (如 ease-out)和开始与结束慢,中间快 (如 ease-in-out):
通常情况下,元素入场(入场动画)时使用结束慢(ease-out),而离场(退场动画)时使用开始慢(ease-in)。从字面上可能不易于理解,拿我们熟悉的轮播图或模态框的进入和退出为例,这些效果实际上都被视为一个 UI 过渡效果。实际上,它有两个过渡,新元素入场,旧元素离场。
这意味着新元素入场,通常应该是逐渐停止,你需要一个 ease-out (例如,模态框从屏幕外飞入屏幕中的效果);旧元素离场,则相反,它应该开始缓慢,这就需要一个 ease-in (例如,模态框在关闭时,它从屏幕中向屏幕外飞出的效果)。
这两者结合在一起会创造出一个无缝衔接的动画效果。
你可能已经感受到了,速度和时间对于动画是多么的重要,正因为如此,才花了很多课时来阐述它们:
- CSS 缓动函数基础:为 Web 动画注入灵魂
- 使用 cubic-bezier() 函数创建高级动画
- 使用 steps() 函数创建分段动画
- 使用 linear() 函数创建令人惊叹的动画曲线
- 深入了解 CSS 动画的持续时间和延迟时间
依赖硬件加速
我曾在《制作流畅动画的技巧与实践》课程中强调过,并非所有 CSS 属性在所有设备和 Web 浏览器上都能平滑地进行动画或过渡。而一个动画能不能给用户带来一个好的体验,还取决于该动画是否流畅,为此,Web 开发者和动画师在创作一个动画时,都以 60fps 的速度为目标。往往通过硬件加速来实现最流畅、最高帧率的动画效果。
庆幸的是,CSS 只有少数几个属性能够利用设备的硬件加速。这意味着,Web 浏览器可能借助设备的图形处理单元(GPU)来加速渲染,从而使渲染速度显著提高,变得更加流畅,但仅在某些条件下才能实现。
CSS 中总是可以进行硬件加速的属性是 transform (包括所有形式的平移、缩放和旋转,以及它们的 3D 对应项)和 opacity 。有时某些 SVG 属性、filter (具体取决于浏览器和滤镜类型)、其他一些非 CSS 的东西,比如 canvas 和 WebGL,也可以利用硬件加速。
然而,这里不会详细介绍这些内容,因为这些内容在《制作流畅动画的技巧与实践》课程中有相应的详细阐述!
简而言之,transform 比你想象的要强大,它除了可以移动、旋转和缩放一个元素之外,它还可以改变元素的大小和形状等。最重要的是,应用 transform 不会影响布局,这最小化了重新计算的次数,保持浏览器的平稳运行,而且,你可以通过 transform 和 opacity 组合来实现在 Web 上许多常见的动画和过渡效果。
不管怎么说,请你记住。在制作动画的时候,无论如何都要避免直接更改元素的大小或位置。如果直接更改可能影响页面上元素的布局的属性,例如 height 、width 、border 、margin 、padding 、top 、right 、bottom 、left 以及它们对应的逻辑属性等,你都可能会因为执行这些更改所需的计算,而造成页面速度明显减慢。因为这些属性会触发页面的重排,重排原本就是一种昂贵的计算,放到动画中,每秒不断的重复相似的计算,那就更昂贵了。
如果确实需要对这些属性进行动画处理,请务必在每个可能的设备和浏览器上进行测试。据我了解,Safari 在默认情况下处理动画的性能表现非常糟糕,特别是在 iOS 上。Firefox 紧随其后,但如果你只在高性能设备上进行测试,你可能不会意识到这一点。世界上还有很多人在使用低端的 Android 设备。请不要忽略对这些设备的测试。
不过,大家需要知道的是,硬件加速并不是制作流畅动画的万能钥匙。仅仅因为某些东西可以进行硬件加速,并不意味着它真会被硬件加速。最终的决定权在浏览器手中。浏览器可能选择放弃硬件加速的潜在原因是:GPU 更快,但它也消耗更多能量。因此,如果设备电量不足或处于省电模式时,浏览器可能会选择电池而不是图形性能。
我们来看一个简单的示例。
上面示例使用了两种不同的方式实现了同一个动画效果。左侧是对 box-shadow 属性进行动画处理,在不同帧改变 box-shadow 属性的值,从而实现一个呼吸灯的动画效果:
@layer animation {
@keyframes ping {
0% {
box-shadow: 0 0 0 0 rgb(136 80 211 / 1);
}
75%,
100% {
box-shadow: 0 0 0 10px rgb(136 80 211 / 0);
}
}
.ping {
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
}右侧是对伪元素 ::after 的 scale 和 opacity 属性进行动画处理:
@layer animation {
@keyframes ping {
75%,
100% {
scale:2;
opacity: 0;
}
}
.ping::after {
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
}简单地看一下这两种方式的性能瀑布图:
很明显,动画化 box-shadow 明显触发了更多的重绘,而动画为伪元素的 opacity 和 scale 触的重绘较少。通过仅在动画过程中更改这两个属性(opacity 和 scale),我们最小化了重绘的次数(以及浏览器必须执行的工作量)。
我想通过这个示例告诉大家,CSS 就是这么神奇,同一种效果我们有很多种不同的方式可以实现,虽然视觉上呈现几乎相似,可实际上背后差异就非常大了。动画更是如此!
慎用 will-change
很多 Web 对于 will-change 属性都不会感到太陌生。只不过大部分 Web 开发者(包括我)都会认为 will-change 可以让动画变得流畅。然而,will-change 并不是万能药。实际上,如果过度使用,反而会损害页面的性能。
我个人的建议是:“如果你对 will-change 不了解,请慎用它,尤其是在动画创作中”。这是因为浏览器对每个应用 will-change 的元素会创建一个新层(有点像一个新的 z-index 级别),使复合变得更加复杂。谨慎使用时,这种交互是值得的。然而,如果使用不当,它可能会为浏览器带来更多的工作(计算量会更大)。
大家需要知道的是,在使用 will-change 时,不要这样做:不要在动画或过渡的地方使用 will-change ;相反,在动画或过渡之前使用它。will-change 应该告诉浏览器即将发生的变化,以便它可以为其做好准备。因为浏览器无法为当前正在发生或已经发生的变化做准备。
例如,不要直接在动画发生的地方添加它到元素,如下所示:
.element:hover {
will-change: color;
color: red;
}这样做实际上弊大于利,will-change 是将来时态,因此在动画发生时不应用它。而是在元素的初始状态添加它,此时尚未发生任何变化,如下所示:
.element {
will-change: color;
transition: color 300ms ease-in-out;
}
.element:hover {
color: red;
}或者将 will-change 放在 .element 的父元素上,如下所示:
.parent-element:hover {
will-change: opacity;
}
.element:hover {
transition: opacity .25s ease-in-out;
}这样可以让浏览器提前知道要优化这个变化。请记住,只有在动画不流畅或闪烁时,才应将其视为最后的手段。
而且,以下是这些情境中,你应该避免使用 will-change:
- 在动画或过渡期间: 不要在元素正在进行动画或过渡时应用
will-change。只有在变化即将发生或可能在不久的将来发生时使用will-change,以便浏览器有足够的时间优化其渲染。在变化已经发生或不再即将发生时使用will-change可能会对浏览器造成压力,并影响性能。 - 变化不可预测: 如果元素的变化是不可预测的,可能会经常或意外发生,那么使用
will-change可能不值得,因为它可能会导致不必要的性能开销。 - 小元素: 对于页面上的小元素,
will-change可能会适得其反,因为优化小元素的渲染可能带来的性能收益可能微乎其微。 - 不可动画的属性: 如果将
will-change应用于不可动画的属性,如font-family或display,它可能对性能没有影响,甚至可能对性能产生负面影响。 - 滥用: 滥用
will-change实际上可能会损害性能而不是改善性能。应避免在页面上的多个元素上应用will-change。这可能会导致浏览器占用机器资源,从而减慢页面加载时间。相反,只在可能很快发生变化并从优化中受益的元素上使用它。
当然,最理想的情况是:当与不影响布局的属性一起使用时,这可以让浏览器完全将计算卸载到GPU上,以获得最高的帧率和最顺畅的性能。不过,我还是要再次强调:will-change 旨在作为最后的手段使用,以尝试解决现有的性能问题。不应该用于预测性能问题。
有关于
will-change更详细的介绍,请参阅《制作流畅动画的技巧与实践》课程!
少即是多
很多时候,我们都可以使用“少即是多”这个论述来终结一切。因为“少”与“多”就是相生相克的,主要是因为这个原则在不同的上下文和设计场景中可能被解读为相互矛盾的指导方针。具体原因如下:
- 简单与表达力的平衡:“少即是多”强调简单、精炼的设计,但在某些情况下,设计师可能需要通过更复杂的元素或动画来传达特定的信息或情感。这时候,简化设计可能会牺牲掉表达力
- 用户期望的可预测性:简单的设计通常更易于用户理解和操作,但在一些创新性的应用中,用户可能期望看到更复杂、令人惊喜的交互和动画。这时,“少即是多”可能与创新性和独特性相矛盾
- 适应不同的品牌风格:一些品牌注重极简主义,而另一些可能更喜欢丰富多彩的设计。因此,“少即是多”对于品牌来说,可能并不总是适用
- 上下文和目标受众:“少即是多”可能在特定的上下文和目标受众中更为有效,但在其他情境下,可能需要更为复杂和独特的设计来吸引注意力
总的来说,“少即是多”是一个设计指导原则,但在实际应用中,设计师需要灵活运用,权衡简洁性与复杂性,以确保最终的设计能够达到预期的效果。在某些情况下,少量的复杂元素可能更有利于传达设计的意图。
当涉及到动画制作时,"少即是多"的原则强调的是在动画效果中保持简洁和精炼,以确保用户体验的清晰度和可理解性。以下是一个实际案例,说明如何应用这一原则。考虑一个简单的按钮,悬停时应用缩放效果,使其看起来更具交互性。
.button {
transition: scale 0.3s ease;
&:hover {
scale: 1.1;
}
}我们使用了 transition 属性,定义了 scale 属性的过渡变化在 0.3s 内以 ease 缓动曲线进行。当用户的鼠标悬浮在按钮上时,按钮会放大到 1.1 倍,创造了一个简单而明显的悬浮动效。
在这个案例中,"少即是多"的原则可以通过几个方面体现:
- 单一属性变化: 我们只改变了按钮的
scale属性,而没有涉及到其他属性,使按钮的悬浮效果更为集中和明确。 - 简单而明显的变化: 缩放是一种直观的变化,不需要用户花费过多认知资源就能理解。这与动画的目标相符。
- 短暂而流畅的过渡: 过渡时间被设置为
0.3s,足够产生效果,但又不至于太长。短暂的动画有助于保持用户的关注。
我们通过精心选择动画属性和过渡效果,确保了按钮的悬浮效果既引人注目又不会分散用户的注意力。这就是"少即是多"原则在实际动画制作中的应用。
不过,很多 Web 开发者很容易被带入到另一种情境中,认为页面上的每一件事都应动画化处理。认为只有这样做,页面才显得炫酷,才能体验自己的技能,尤其是动画创作的技能。
我曾经也犯过这个错误,好在我团队有很多优秀的设计师和用户体验师,他们很快纠正了我这个错误!
事实上,在使用动画时,通常情况下,低调(精减或精致)比过于张扬更好。因为元素在动画过程中发生的变化越大,或者说页面中动画化的对象(元素)越多,动画所带来的风险也越大,更易于给应用带来负作用。除非你正在开发一个人个网站,你只是想要点创意,否则过多的动画总是弊大于利。
“少即是多”中的这个“多”有两层意思,其中一层就是字面上的意思,就是指数量过多。比如,页面上同时有多个动画元素或者在同一个元素上同时有多个动画。其另外一层意思是大或者说“夸张”。例如:
- 如果你正在制作一个淡入的动画效果,通常情况下会将
opacity属性的值从0过渡到1(反之亦是),那么你可以尝试将这个范围变小一点,比如将opacity的值从.4过渡到1 - 如果你正在将元素缩放到全尺寸,通常情况下会将
scale属性的值从0过渡到1,那么你可以尝试使其略微小一些,比如从.5到1,这样可以避免元素因太小以至于看不到 - 如果你正在给元素添加滑动效果,我发现大多数情况下,元素移动的范围大约在
5px ~ 40px之间。任何更小,移动可能会太微妙,甚至用户察觉不到;更大的话,滑动效果可能会变得笨拙
以下是一个简单示例,同时对元素的 opacity 、translate 和 scale 进行动画处理:
@layer animation {
@keyframes fadeIn-a {
0%,
10% {
opacity: 0;
translate: 0 8rem;
scale: 0;
}
30%,
100% {
opacity: 1;
translate: 0 0;
scale: 1;
}
}
@keyframes fadeIn-b {
0%,
14% {
opacity: 0;
translate: 0 1rem;
scale: 0.95;
}
15% {
opacity: 0.25;
}
30%,
100% {
opacity: 1;
translate: 0 0;
scale: 1;
}
}
.element--a {
animation: fadeIn-a 3s ease-out infinite;
}
.element--b {
animation: fadeIn-b 3s cubic-bezier(0.14, 0.18, 0.16, 1.02) infinite;
}
}相比而言,A 比 B 动画幅度要大得多,B 元素上的 opacity 、translate 和 scale 属性在动画化时减弱了一些,并加快了一些。至于哪个效果更好?我只能说找到动画的感觉,找到动画刚好能够产生效果的点。
总体而言,"少即是多"的技巧是一种在Web动画制作中取得平衡的方法,该原则的好处在于提供简洁、直观的用户体验,同时保持页面性能。然而,过于简化或过度复杂的动画都可能带来问题,关键在于在具体情境中灵活应用这一原则。
构建包容性和可访问性的 CSS 动画
Web 最大的特色之一就是它的包容性,所有人都平等享受 Web 带来的一切,动画亦是如此。虽然动画在 Web 上应用得当时,它们能吸引用户的注意力,能更好的帮助用户更好的理解界面的工作方式等。但是,在给 Web 添加动画时也需谨慎,因为一些患有前庭障碍的用户,很容易因动画感到不适。在最糟糕的情况下,动画还可能引发晕动症、恶心等。
引起不适的程度以及触发原因因人而异。因此,我们应该提供一个更具包容性和可访问性的动画。换句话说,我们应该尊重用户的偏好。这也意味着,我们构建的动画也应该满足用户的偏好,即满足用户减少运动的偏好。
在 CSS 中,我们使用媒体查询 prefers-reduced-motion 可以很容易做到这一点:
@media (prefers-reduced-motion: reduce) {
*,
::before,
::after {
animation-delay: -1ms !important;
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
background-attachment: initial !important;
scroll-behavior: auto !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
}
}不过,我们需要知道的是:减少运动作并不意味着没有运动,甚至根本没有动画。我经常使用的一种技术是:在检测到减少运动偏好时,更改关键帧动画。例如:
@keyframes slideIn {
from {
opacity: 0;
translate: 0 2rem;
}
}
@keyframes slideInReduced {
from {
opacity: 0;
}
}
.animated-thing {
animation-name: slideIn;
}
@media (prefers-reduced-motion) {
.animated-thing {
animation-name: slideInReduced;
}
}此外,有些情况下保留动作可能更好。例如,如果你在后台加载某些内容时显示一个进度条,或者在音频或视频文件上显示一个播放指示器,那是 UI 中的一个关键信息。与其完全消除该指示器,不如考虑一种使运动变得微妙而不引人注目的方式。或者,可能更好的方式是允许用户隐藏运动。或者,以不同的方式表示信息可能是更好的选择(例如,使用数字百分比计数器而不是动画条)。
至少,用户应该能够暂停所有连续的动画,包括视频和 GIF。然而,理想情况下,我们应该根据他们的设备偏好来预测他们的需求,而不是迫使他们处理他们已经表示他们宁愿不看到的东西。
有关于这方面更深入的探讨,请阅读《提升可访问性动画的关键技巧》!
小结
Web 动画有许多好处。它们使你的界面栩栩如生,让用户感到愉悦。由于CSS发展迅速,实现 Web 动画并控制它们的感觉比以往任何时候都更加容易。
制作动画时,有几个关键的技巧和注意事项,可以帮助你创造出吸引人、流畅且有意义的动画:
- 选择合适的动画类型: 不同的场景和目的需要不同类型的动画。考虑使用过渡、关键帧动画、滚动故事(scrollytelling)等技术,以匹配你的设计和用户体验目标。
- 关注细节: 注意动画中的细节,这有助于创造更真实、自然的体验。细节是体验的“黄金线”,可以增加真实感并提高用户的参与度。
- 选择适当的缓动函数: 缓动函数影响动画的速度变化。使用合适的缓动函数可以使动画更加自然,例如使用
ease-in-out来模拟物体的加速和减速。 - 利用硬件加速: 某些 CSS 属性可以利用硬件加速,提高动画的流畅性。常见的硬件加速属性包括
transform和opacity。避免直接改变可能影响布局的属性,以提高性能。 - 遵循“少即是多”原则: 不要过度使用动画。适度的动画能够增强用户体验,但过多的动画可能会让界面显得杂乱,产生负面效果。
- 考虑可访问性: 确保你的动画对于所有用户都是可访问的。使用
prefers-reduced-motion媒体查询来检测用户是否希望减少动画,为他们提供更舒适的体验。 - 制定动画指南: 在大型项目中,建立动画指南和设计系统。这些文档可以包含动画的目的、类型、时长、缓动函数等信息,确保整个团队在使用动画时保持一致性。
- 考虑可持续性: 动画应该是可持续的,不应该在短时间内引起视觉疲劳。确保动画的频率和强度是可接受的,避免不必要的刺激。
总的来说,制作动画需要综合考虑设计目标、用户体验和性能。通过选择合适的技术和注意细节,你可以创建出引人入胜的动画效果,提升用户与界面的互动体验。